package org.vt.ece3574.pictoslider.puzzle;

import java.util.LinkedList;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ImageView;
import android.widget.TableLayout;
import android.widget.TableRow;
import org.vt.ece3574.pictoslider.GameScreen;
import org.vt.ece3574.pictoslider.HighScoreManager.Difficulty;

/**
 * This class represents a puzzle, which can be placed on to the Game Screen.
 * 
 * @author Michael Drescher
 */
public class Puzzle extends TableLayout {

	/**
	 * Keeps track of the number of pieces in their correct positions. Used to
	 * determine whether or not a puzzle is solved.
	 */
	private int pieceInPositionCount;
	/**
	 * Number of rows in the puzzle.
	 */
	private int puzzleHeight;
	/**
	 * Number of columns in the puzzle.
	 */
	private int puzzleWidth;
	/**
	 * The Parent Activity for this puzzle. A reference is needed so we can
	 * notify the parent when the puzzle has been solved.
	 */
	private GameScreen parentActivity;
	/**
	 * A reference to the Bitmap used as this puzzle's image.
	 */
	private Bitmap image;
	/**
	 * Keeps track of the number of moves made so far (used for high scores)
	 */
	private int numSwaps = 0;
	/**
	 * The difficulty of the puzzle.
	 */
	private Difficulty difficulty;

	// Layout Parameters for the Puzzle
	/**
	 * Number of pixels between pieces
	 */
	private final static int PIECE_MARGIN = 2;
	/**
	 * Layout Weight for each piece
	 */
	private final static int PIECE_WEIGHT = 1;

	/**
	 * Constructs a puzzle with the following parameters.
	 * 
	 * @param context
	 *            - The context for the puzzle and all its pieces
	 * @param height
	 *            - The puzzle is this many pieces tall
	 * @param width
	 *            - The puzzle is this many pieces wide
	 * @param p
	 *            - The picture source for this puzzle
	 * @author Michael Drescher
	 */
	public Puzzle(Context context, int height, int width, Bitmap picture,
			GameScreen g) {
		super(context);

		puzzleWidth = width;
		puzzleHeight = height;

		// Determine the puzzle difficulty from the height parameter
		switch (height) {
		case 3:
			difficulty = Difficulty.EASY;
			break;
		case 4:
			difficulty = Difficulty.MEDIUM;
			break;
		case 5:
			difficulty = Difficulty.HARD;
			break;
		case 6:
			difficulty = Difficulty.CRAZY;
			break;
		default:
			difficulty = Difficulty.EASY;
			break;
		}

		pieceInPositionCount = width * height;
		parentActivity = g;

		// Get dimensions of the screen
		DisplayMetrics metrics = new DisplayMetrics();
		parentActivity.getWindowManager().getDefaultDisplay()
				.getMetrics(metrics);

		// Get screen height and width and set pictureSize to minimum of the two
		int pictureSize = (metrics.heightPixels < metrics.widthPixels) ? metrics.heightPixels
				: metrics.widthPixels;

		// Make a screen-sized bitmap for the puzzle
		image = Bitmap.createScaledBitmap(picture, pictureSize, pictureSize,
				false);
		picture.recycle();
		picture = null;

		// Get the width and height of each piece in the puzzle
		int pieceWidth = pictureSize / width;
		int pieceHeight = pictureSize / height;

		// Construct each piece, and add them in order to a temporary list
		PuzzlePiece lastPiece = null;
		List<PuzzlePiece> list = new LinkedList<PuzzlePiece>();
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				PuzzlePiece piece = new PuzzlePiece(context,
						Bitmap.createBitmap(image, PIECE_MARGIN + j
								* pieceWidth, // X starting position
								PIECE_MARGIN + i * pieceHeight, // Y starting
																// position
								pieceWidth - 2 * PIECE_MARGIN, // Width
								pieceHeight - 2 * PIECE_MARGIN), i, j); // Height,
																		// row,
																		// and
																		// column
				piece.setVisibility(VISIBLE);
				piece.setPadding(PIECE_MARGIN, PIECE_MARGIN, PIECE_MARGIN,
						PIECE_MARGIN);
				piece.setScaleType(ImageView.ScaleType.FIT_CENTER);
				piece.setLayoutParams(new TableRow.LayoutParams(
						TableRow.LayoutParams.MATCH_PARENT,
						TableRow.LayoutParams.MATCH_PARENT,
						(float) PIECE_WEIGHT));
				list.add(piece);
				// Store a reference to the blank piece for later.
				if (i * j == (height - 1) * (width - 1)) {
					lastPiece = piece;
				}
			}
		}

		// Get the order for the pieces, and add them to the puzzle in the
		// specified order.
		int order[][] = ArrayMagic.create(width);
		for (int i = 0; i < height; i++) {

			// Create a new row in the puzzle, to be populated with PuzzlePieces
			TableRow temp = new TableRow(context);
			temp.setLayoutParams(new TableLayout.LayoutParams(
					TableLayout.LayoutParams.MATCH_PARENT,
					TableLayout.LayoutParams.MATCH_PARENT, (float) PIECE_WEIGHT));

			// Add Pieces to the row
			for (int j = 0; j < width; j++) {
				PuzzlePiece piece = list.get(order[i][j]);
				temp.addView(piece);
			}
			// Add the row to the puzzle
			addView(temp);
		}
		// Make the bottom right piece and all its neighbors moveable
		lastPiece.setMoveable(true);
		lastPiece.setVisibility(INVISIBLE);
		PuzzlePiece[] neighbors = getNeighborsForPiece(lastPiece);
		for (int i = 0; i < neighbors.length; i++) {
			if (neighbors[i] != null)
				neighbors[i].setMoveable(true);
		}
	}

	/**
	 * @return The image used for this puzzle.
	 */
	public Bitmap getBitmap() {
		return image;
	}

	/**
	 * @return The puzzle difficulty.
	 */
	public Difficulty getDifficulty() {
		return difficulty;
	}

	/**
	 * Retrieves the PuzzlePiece's neighbors in an array. The first two pieces
	 * in the array are horizontally adjacent, while the last two pieces are
	 * vertically adjacent.
	 * 
	 * @param piece
	 *            - The piece whose neighbors will be returned.
	 * @return An array of 4 PuzzlePieces referring to each of piece's
	 *         neighbors. If a neighbor does not exist, that element of the
	 *         array is null. <LI>neighbors[0] --> Left neighbor</LI> <LI>
	 *         neighbors[1] --> Right neighbor</LI> <LI>neighbors[2] --> Bottom
	 *         neighbor</LI> <LI>neighbors[3] --> Top neighbor</LI>
	 */
	protected PuzzlePiece[] getNeighborsForPiece(PuzzlePiece piece) {
		PuzzlePiece[] neighbors = new PuzzlePiece[4];
		TableRow temp = (TableRow) piece.getParent();
		int row = this.indexOfChild(temp);
		int column = temp.indexOfChild(piece);
		neighbors[0] = getPieceAt(row, column - 1);
		neighbors[1] = getPieceAt(row, column + 1);
		neighbors[2] = getPieceAt(row - 1, column);
		neighbors[3] = getPieceAt(row + 1, column);
		return neighbors;
	}

	/**
	 * Retrieves the piece at the selected location in the puzzle.
	 * 
	 * @param row
	 *            - The row of the desired piece.
	 * @param column
	 *            - The column of the desired piece.
	 * @return The piece at the specified location, or null if the location is
	 *         invalid
	 */
	public PuzzlePiece getPieceAt(int row, int column) {
		PuzzlePiece piece;
		TableRow temp = (TableRow) this.getChildAt(row);
		if (temp != null) {
			piece = (PuzzlePiece) temp.getChildAt(column);
		} else
			return null;
		return piece;
	}

	/**
	 * Swaps the position of two pieces in the puzzle.
	 * 
	 * @param piece1
	 * @param piece2
	 */
	protected void swap(PuzzlePiece piece1, PuzzlePiece piece2) {

		boolean piece1WasInPosition = piece1.checkIfInPosition();
		boolean piece2WasInPosition = piece2.checkIfInPosition();

		// Swap the pieces
		if ((piece1 != null) && (piece2 != null)) {
			TableRow tableRow1 = (TableRow) piece1.getParent();
			TableRow tableRow2 = (TableRow) piece2.getParent();
			if ((tableRow1 != null) && (tableRow2 != null)) {
				int pos1 = tableRow1.indexOfChild(piece1);
				int pos2 = tableRow2.indexOfChild(piece2);
				tableRow1.removeView(piece1);
				tableRow2.removeView(piece2);

				// Handle case where both pieces are in the same row, so as to
				// prevent an index out of bounds error.
				if (tableRow2.equals(tableRow1) && (pos1 > pos2))
					pos1 = pos1 - 1;

				tableRow1.addView(piece2, pos1);
				tableRow2.addView(piece1, pos2);
				piece1.setColumn(tableRow2.indexOfChild(piece1));
				piece2.setColumn(tableRow1.indexOfChild(piece2));
				piece1.setRow(this.indexOfChild(tableRow2));
				piece2.setRow(this.indexOfChild(tableRow1));
				numSwaps++;
				parentActivity.notifySwapped();
			}
		}

		// Update the count of the number of pieces in position, if this count
		// has changed
		if (piece1WasInPosition != piece1.checkIfInPosition()) {
			if (piece1.checkIfInPosition()) {
				pieceInPositionCount++;
			} else {
				pieceInPositionCount--;
			}
		}
		if (piece2WasInPosition != piece2.checkIfInPosition()) {
			if (piece2.checkIfInPosition()) {
				pieceInPositionCount++;
			} else {
				pieceInPositionCount--;
			}
		}

		if (isSolved()) {
			parentActivity.notifyPuzzleSolved();
		}
	}

	/**
	 * @return True if the puzzle has been solved, false otherwise.
	 */
	public boolean isSolved() {
		return (pieceInPositionCount == puzzleHeight * puzzleWidth);
	}

	/**
	 * @param g
	 *            - The parent activity for this puzzle.
	 */
	public void setParentActivity(GameScreen g) {
		parentActivity = g;
	}

	/**
	 * @return The number of moves made by the user on this puzzle.
	 */
	public int getNumSwaps() {
		return numSwaps;
	}

	/**
	 * Sets the onTouchListener for every piece in the puzzle to handler.
	 * 
	 * @param handler
	 */
	public void setTouchHandler(PuzzleTouchHandler handler) {
		for (int i = 0; i < this.getChildCount(); i++) {
			TableRow temp = (TableRow) this.getChildAt(i);
			for (int j = 0; j < temp.getChildCount(); j++) {
				View v = temp.getChildAt(j);
				v.setOnTouchListener(handler);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.widget.TableLayout#onLayout(boolean, int, int, int, int)
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
	}
}
